// exception.cc 
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include <stdio.h>        // FA98
#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "filesys.h"
#include "addrspace.h"   // FA98
#include "sysdep.h"   // FA98
#include "synch.h"

// begin FA98

static int SRead(int addr, int size, int id);
static void SWrite(char *buffer, int size, int id);
static int SExec(int);
static int SExit(int);
void processCreator(int);
int processed;
// end FA98

//start code for making PCBList class
//it's a linked list of threads

//----------------------------------------------------------------------
// ExceptionHandler
// 	Entry point into the Nachos kernel.  Called when a user program
//	is executing, and either does a syscall, or generates an addressing
//	or arithmetic exception.
//
// 	For system calls, the following is the calling convention:
//
// 	system call code -- r2
//		arg1 -- r4
//		arg2 -- r5
//		arg3 -- r6
//		arg4 -- r7
//
//	The result of the system call, if any, must be put back into r2. 
//
// And don't forget to increment the pc before returning. (Or else you'll
// loop making the same system call forever!
//
//	"which" is the kind of exception.  The list of possible exceptions 
//	are in machine.h.
//----------------------------------------------------------------------


void
ExceptionHandler(ExceptionType which)
{
	int type = machine->ReadRegister(2);
	int arg1 = machine->ReadRegister(4);
	int arg2 = machine->ReadRegister(5);
	int arg3 = machine->ReadRegister(6);
	int Result;
	int i, j;
	char *ch = new char [500];
	
	switch (which)
	{
		printf("\nException (%d) raised in %s PID: %d.\n", which, currentThread->getName(), currentThread->getThreadID());
		case NoException :
			break;
		case SyscallException :
			// for debugging, in case we are jumping into lala-land
			// Advance program counters.
			
			IntStatus oldLevel; 
			oldLevel = interrupt->SetLevel(IntOff);
			
			//printf("\nSyscall (%d) raised in %s PID: %d.\n", type, currentThread->getName(), currentThread->getThreadID());
			
			machine->registers[PrevPCReg] = machine->registers[PCReg];
			machine->registers[PCReg] = machine->registers[NextPCReg];
			machine->registers[NextPCReg] = machine->registers[NextPCReg] + 4;
			
			(void) interrupt->SetLevel(oldLevel);
	
			switch (type)
			{
				case SC_Halt:
					printf("SYSTEM CALL (%d): Halt, called in %s by process %d.\n", type, currentThread->getName(), currentThread->getThreadID());
					DEBUG('t', "Shutdown, initiated by user program.\n");
					interrupt->Halt();
					break;

			
				case SC_Read :
					printf("SYSTEM CALL (%d): Read, called in %s by process %d.\n", type, currentThread->getName(), currentThread->getThreadID());
					if (arg2 <= 0 || arg3 < 0)
					{
						printf("\nRead 0 byte.\n");
					}
					
					Result = SRead(arg1, arg2, arg3);
					machine->WriteRegister(2, Result);
					DEBUG('t',"Read %d bytes from the open file(OpenFileId is %d)", arg2, arg3);
					break;

				case SC_Write :
					printf("SYSTEM CALL (%d): Write, called in %s by process %d.\n", type, currentThread->getName(), currentThread->getThreadID());
					for (j = 0; ; j++) 
					{
						if(!machine->ReadMem((arg1+j), 1, &i))
						{
							j=j-1;
						}
						
						else
						{
							ch[j] = (char) i;
							
							if (ch[j] == '\0') 
								break;
						}
					}
					
					if (j == 0)
					{
						printf("\nWrite 0 bytes.\n");
						SExit(-1);
					} 
					
					else 
					{
						DEBUG('t', "\nWrite %d bytes from %s to the open file(OpenFileId is %d).", arg2, ch, arg3);
						SWrite(ch, j, arg3);
					}
					
					break;

				case SC_Exec :
					printf("SYSTEM CALL (%d): Exec, called in %s by process %d.\n", type, currentThread->getName(), currentThread->getThreadID());
					processed = SExec(arg1);
					machine->WriteRegister(2, processed);
					
					if (processed == -1)
					{
						printf("Exec failed\n");
					}
					
					break;

				case SC_Join :				
					int result;
					printf("SYSTEM CALL (%d): Join, called in %s by process %d.\n", type, currentThread->getName(), currentThread->getThreadID());
					IntStatus oldLevel; 
					oldLevel = interrupt->SetLevel(IntOff);
					
					result = PCB->ThreadJoin(arg1, currentThread->getThreadID());
					(void) interrupt->SetLevel(oldLevel);	
					
					if (result != -1)
					{
						//printf("Joining process %d with process %d.
						printf("PID: %d was successfully joined from %s PID: %d.", arg1, currentThread->getName(), currentThread->getThreadID());
						machine->WriteRegister(2, result); 
						printf("%s PID: %d has been put to sleep.\n", currentThread->getName(), currentThread->getThreadID());
						IntStatus oldLevel; 
						oldLevel = interrupt->SetLevel(IntOff);
						currentThread->Sleep();				
						(void) interrupt->SetLevel(oldLevel);		
					}
					
					else
					{
						machine->WriteRegister(2, -1);
						printf("Could not join on invalid PID: %d from %s PID: %d.\n", arg1, currentThread->getName(), currentThread->getThreadID());
					}
			
					break;

				case SC_Yield :
					printf("SYSTEM CALL: Write, called by thread %d.\n", currentThread->getThreadID());
					//printf("Yield by \"%s\" PID: %d.\n", currentThread->getName(), currentThread->getThreadID());					
					currentThread->Yield();
					break;

				case SC_Exit :
					SExit(arg1);
					break;
					
				default:
					printf("Unprogrammed syscall.\n");
					break;
			}
			
			break;     

	case ReadOnlyException :
		puts ("ReadOnlyException");
		if (currentThread->getName() == "main")
		// ASSERT(FALSE);  //Not the way of handling an exception.
		SExit(-1);
		break;
	case BusErrorException :
		puts ("BusErrorException");
		if (currentThread->getName() == "main")
		// ASSERT(FALSE);  //Not the way of handling an exception.
		SExit(-1);
		break;
	case AddressErrorException :
		puts ("\nAddressErrorException\n");
		//ASSERT(FALSE);
		if (currentThread->getName() == "main")
		// ASSERT(FALSE);  //Not the way of handling an exception.
		SExit(-1);
		break;
	case OverflowException :
		puts ("OverflowException");
		if (currentThread->getName() == "main")
		// ASSERT(FALSE);  //Not the way of handling an exception.
		SExit(-1);
		break;
	case IllegalInstrException :
		puts ("IllegalInstrException");
		//ASSERT(FALSE);
		if (currentThread->getName() == "main")
		// ASSERT(FALSE);  //Not the way of handling an exception.
		SExit(-1);
		break;
	case NumExceptionTypes :
		puts ("NumExceptionTypes");
		if (currentThread->getName() == "main")
		// ASSERT(FALSE);  //Not the way of handling an exception.
		SExit(-1);
		break;
	case PageFaultException :
		puts ("PageFaultException");
		int virtualAddr; 
		virtualAddr = machine->ReadRegister(BadVAddrReg);
		
		//printf("PageFaultException raised by %d at address: %d, page %d.\n", currentThread->getThreadID(), virtualAddr, virtualAddr / PageSize);
        printf("Page fault: Process %d requests virtual page %d.\n", currentThread->getThreadID(), virtualAddr / PageSize);
		
        if (printSpecial) {
            numPageFaults++;
            printf("Current number of page faults: %d\n", numPageFaults);
        }
        
		if (!IPT->Mark(currentThread, virtualAddr))
		{
			printf("The Inverted Page Table has no free pages to allocate.\n");
			
			if (virtualMemType == 0)
			{
				//printf("-V option 0: Demand Paging was specified. NachOS will now terminate.\n");
				ASSERT(FALSE);
			}
			
			else if (virtualMemType == 1)
			{
				//printf("-V option 1: FIFO Page Replacement was specified. But we still need to do this.\n");
				IPT->FIFOSwap(currentThread, virtualAddr);
				
			}
			
			else if (virtualMemType == 2)
			{
				//printf("-V option 2: Random Page Replacement was specified. But we still need to do this.\n");
				IPT->RandomSwap(currentThread, virtualAddr);
				
			}
			
			else
			{
				//printf("Invalid -V option %d: using default Demand Paging. NachOS will now terminate.\n", virtualMemType);
				ASSERT(FALSE);
			}

		}

		break;

	default :
		printf("Unexpected user mode exception %d %d\n", which, type);
		if (currentThread->getName() == "main")
			SExit(-1);
	break;
	}
	
	delete [] ch;
}

static int SExec(int buffadd)
{
    int ch;
	char *filename = new char[4001]; //Filename length may not exceed 4000 characters.
	
	if (!machine->ReadMem(buffadd, 1, &ch))
	{
		if(!machine->ReadMem(buffadd, 1, &ch))
	  		return -1;
	}
	 
	int i = 0;

	while (ch != 0)
	{
		filename[i] = (char)ch;
	 	buffadd += 1;
	  	i++;
	  	
	  	if (i > 4000)
	  	{
	  		printf("Filename length exceeded maximum length of 4000 characters.\n");
	    	return -1;	  		
	  	}
	  	
		if (!machine->ReadMem(buffadd, 1, &ch))
		{
			if(!machine->ReadMem(buffadd, 1, &ch))
		  		return -1;
		}   	
	}

	filename[i] = (char) 0;
	OpenFile* executable = fileSystem->Open(filename);
	AddrSpace* space;

	if (executable == NULL) 
	{
	     printf("Unable to open file named %s.\n", filename);
             return -1;
	}

	Thread* newProcess = new Thread(filename);
	
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	int processID = newProcess->getThreadID();
	space = new AddrSpace(executable);
	
	space->CreateSwapFile(processID);
	
	if (space->Valid() == 1)
	{
		newProcess->space = space;

		IntStatus oldLevel = interrupt->SetLevel(IntOff);
		
		PCB->ThreadBegin(newProcess);
		
		(void) interrupt->SetLevel(oldLevel);
		
		printf("Executing user program %s with PID: %d.\n", newProcess->getName(), processID);
		newProcess->Fork(processCreator, 0);
		
		return processID;		
	}
	
	else
	{
		(void) interrupt->SetLevel(oldLevel);
		printf("Space creation failed\n");
		return -1;
	}
}

static int SExit(int exitVal)
{ 
	if(exitVal == NULL)
	{
		printf("User program %s PID: %d exited normally. Exit code: 0.\n", currentThread->getName(), currentThread->getThreadID()); 
		machine->WriteRegister(2, 0);
		PCB->ThreadEnd(currentThread->getThreadID());
	} 
	
	else if(exitVal != -1) 
	{
		printf("User program %s PID: %d exited normally. Return value: %d. Exit code: 0.\n", currentThread->getName(), currentThread->getThreadID(), exitVal);
		machine->WriteRegister(2, 0);  
		PCB->ThreadEnd(currentThread->getThreadID());
	}
	
	else 
	{ 
		 printf("User program %s PID: %d exited abnormally. Exit code: 1.\n", currentThread->getName(), currentThread->getThreadID());
		 machine->WriteRegister(2, 1);
		 PCB->ThreadEnd(currentThread->getThreadID());
	}
}

void processCreator(int arg)
{
	currentThread->space->InitRegisters();
	currentThread->space->RestoreState();
	machine->Run();
	
	ASSERT(FALSE);
}

static int SRead(int addr, int size, int id)  //input 0  output 1
{
	char buffer[size+10];
	int num,Result;

	//read from keyboard, try writing your own code using console class.
	if (id == 0)
	{
       		scanf("%s\n",buffer);

		num=strlen(buffer);
		if(num>(size+1)) {

			buffer[size+1] = '\0';
			Result = size+1;
		}
		else {
			buffer[num+1]='\0';
			Result = num + 1;
		}

		for (num=0; num<Result; num++)
		{  machine->WriteMem((addr+num), 1, (int) buffer[num]);
			if (buffer[num] == '\0')
			break; }
		return num;

	}
	//read from a unix file, later you need change to nachos file system.
	else
	{
		for(num=0;num<size;num++){
			Read(id,&buffer[num],1);
			machine->WriteMem((addr+num), 1, (int) buffer[num]);
			if(buffer[num]=='\0') break;
		}
		return num;
	}
}

static void SWrite(char *buffer, int size, int id)
{
	//write to terminal, try writting your own code using console class.
	if (id == 1)
	printf("%s\n", buffer);
	//write to a unix file, later you need change to nachos file system.
	if (id >= 2)
	WriteFile(id,buffer,size);
}

// end FA98

